const TASKS_NUMBER = 50;

const actions = ["+", "-", "*", "/"];
const PMactions = ["+", "-"];
const MDactions = ["*", "/"];
const getRandomNumber = (min, max) =>
  Math.floor(Math.random() * (max - min) + min);
const isMultOrDiv = (op) => MDactions.includes(op);
const isPlusOrMin = (op) => PMactions.includes(op);
const getAnyOperation = () => actions[getRandomNumber(0, 4)];
const getPMOperation = () => PMactions[getRandomNumber(0, 2)];
const getMDOperation = () => MDactions[getRandomNumber(0, 2)];
const getOperationIndexes = (operationsArray, operationRegex) =>
  operationsArray.reduce(
    (prev, curr, index) =>
      curr.match(operationRegex) ? [...prev, index] : prev,
    []
  );

const getMultipyTask = (max = 9, min = 2, A = undefined) => {
  const a = A ?? getRandomNumber(min, max);
  const b = getRandomNumber(min, max);
  return { task: `${a} * ${b}`, answer: a * b, a, b };
};
const getDivisionTask = (max = 9, min = 2) => {
  const task = getMultipyTask(max, min);
  return {
    task: `${task.answer} : ${task.a}`,
    answer: task.b,
    a: task.answer,
    b: task.a,
  };
};

const getComplexTask = (max = 9, min = 2) => {
  let a, b, c, task;
  const op1 = getMDOperation();
  const op2 = getPMOperation();
  c = getRandomNumber(min, max);
  if (op1 === "*") {
    a = getRandomNumber(min, max);
    b = getRandomNumber(min, max);
  } else {
    const divisionTask = getDivisionTask(9, 6);
    a = divisionTask.a;
    b = divisionTask.b;
  }
  task = `${a} ${op1} ${b} ${op2} ${c}`;
  const answer = eval(task);
  return { task, answer, a, b, c };
};

const getOperations = (operationsNumber = 3) => {
  const operations = new Array(operationsNumber).fill(0).map(getAnyOperation);
  const PMIndex = operations.findIndex((e) => e.match(/[+-]/));
  const MDIndex = operations.findIndex((e) => e.match(/[\*\/]/));
  const lastDivIndex = operations.lastIndexOf("/");
  const lastMultIndex = operations.lastIndexOf("*");
  const numberOfDivisions = operations.filter((op) => op === "/").length;
  const numberOfMultiplies = operations.filter((op) => op === "*").length;
  if (numberOfDivisions > 1)
    operations.splice(lastDivIndex, 1, getPMOperation());
  if (numberOfMultiplies > 1)
    operations.splice(lastMultIndex, 1, getPMOperation());
  // if no +- operation - insert one in middle
  if (PMIndex === -1) operations.splice(1, 1, getPMOperation());
  // if no */ operation - insert one in middle
  if (MDIndex === -1) operations.splice(1, 1, getMDOperation());
  return operations;
};
const getBracesPosition = (operations) => {
  const PMIndexes = getOperationIndexes(operations, /[+-]/);
  const MDIndexes = getOperationIndexes(operations, /[\*\/]/);
  const isAllBracesInMDPosition = (braces, MDindexes, PMIndexes) =>
    Math.min(...MDindexes.map((i) => braces[i])) === 1 &&
    Math.min(...PMIndexes.map((i) => braces[i])) === 0;
  const noBracesWereSet = (braces) => Math.max(...braces) === 0;
  const allBracesInAllPositions = (braces) => Math.min(...braces) === 1;
  const twoConsequenceBracesWereSet = (braces) => {
    if (braces.filter((e) => e === 1).length === 1) return false;
    const zeroIndex = braces.findIndex((e) => e === 0);
    return Math.floor(zeroIndex / 2) === 0;
  };
  const twoBracesWereSet = (braces) => braces.filter((e) => e === 1).length > 1;
  const isSetBraces = (i, MDIndexes) =>
    MDIndexes.includes(i) ? 0 : Math.random() > 0.5;
  const braces = new Array(operations.length)
    .fill(0)
    .map((_, i) => (isSetBraces(i, MDIndexes) ? 1 : 0));
  const bracesSettedIndexes = braces.reduce(
    (prev, curr, i) => (curr === 1 ? [...prev, i] : prev),
    []
  );
  /* variants
  [0], [1], 
  [0,0], [0,1], [1,0], *[1,1]*, 
  [0,0,0], [1,0,0], [0,1,0], [0,0,1], [1,1,0], [1,0,1], [0,1,1], *[1,1,1]*
  and so on
  */
  if (allBracesInAllPositions(braces)) {
    // if braces were put in all positions - set first one of MDoperations without braces
    braces[MDIndexes[0]] = 0;
  } else if (noBracesWereSet(braces)) {
    // if no braces were put in all positions - set first one of PMoperations with braces
    braces[PMIndexes[0]] = 1;
  } else if (isAllBracesInMDPosition(braces, MDIndexes, PMIndexes)) {
    braces[MDIndexes[0]] = 0;
    braces[PMIndexes[0]] = 1;
  }
  if (twoBracesWereSet(braces)) braces[bracesSettedIndexes[1]] = 0;
  return braces;
};
const getNumbers = (operations, braces) => {
  const numbers = [];
  let a, b;
  for (let operation of operations) {
    switch (operation) {
      case "/":
        a = numbers.pop() || getRandomNumber(2, 9);
        const task = getMultipyTask(9, 2, a);
        numbers.push(task.answer, task.a);
        break;
      case "*":
      case "+":
      case "-":
        a = numbers.pop() || getRandomNumber(2, 9);
        b = getRandomNumber(2, 10);
        numbers.push(a, b);
        break;
      default:
        break;
    }
  }
  const unfixedNumbers = [...numbers]; // for debuugging
  fixNumbersForDivision(numbers, braces, operations);
  return { numbers, unfixedNumbers };
};

const fixNumbersInPastPosition = (numbers, operations, pos) => {
  if (operations[pos] === "-") {
    numbers[pos] += numbers[pos + 1];
  } else if (operations[pos] === "+") {
    numbers[pos] -= numbers[pos + 1];
  }
  if (numbers[pos] < 0) {
    let t = -numbers[pos];
    numbers[pos] = numbers[pos + 1];
    numbers[pos + 1] = t;
    operations[pos] = "-";
  }
  return numbers;
};

const fixNumbersInPrevPosition = (numbers, operations, pos) => {
  if (operations[pos] === "-") {
    if (numbers[pos + 1] >= numbers[pos]) {
      let a = numbers[pos + 1] + numbers[pos];
      numbers[pos + 1] = numbers[pos];
      numbers[pos] = a;
    } else {
      throw { numbers, operations };
    }
  } else if (operations[pos] === "+") {
    numbers[pos + 1] -= numbers[pos];
  }
  if (numbers[pos + 1] < 0) {
    numbers[pos + 1] = -numbers[pos + 1];
    operations[pos] = "-";
  }
  return numbers;
};

const fixNumbersForDivision = (numbers, braces, operations) => {
  for (let i = 0; i < operations.length; i++) {
    if (operations[i] !== "/") continue;
    if (i === 0 && braces[i + 1] === 1) {
      numbers = fixNumbersInPastPosition(numbers, operations, i + 1);
    } else if (i > 0 && braces[i - 1] === 1) {
      if (numbers[i] / numbers[i + 1] > 10)
        numbers[i] = numbers[i] - numbers[i + 1] * 10;
      numbers = fixNumbersInPrevPosition(numbers, operations, i - 1);
    } else if (i > 0 && braces[i + 1] === 1) {
      numbers = fixNumbersInPastPosition(numbers, operations, i + 1);
    }
  }
  return { numbers, braces, operations };
};

/**
 * Returns task in form `a ? b ? c ? d = `,
 * where ? could be any of [+, -, *, /].
 * Opereations order could be changed with braces
 */
const getComplexTaskWithBraces = (operationsNumber = 3) => {
  const operations = getOperations(operationsNumber);
  const braces = getBracesPosition(operations);
  const { numbers, unfixedNumbers } = getNumbers(operations, braces);
  let task = "";
  for (let i = 0; i < operations.length; i++) {
    if (braces[i] === 1 && i === 0) task += "(";
    if (i === 0) task += numbers[i];
    task += operations[i];
    if (braces[i + 1] === 1 && braces[i] === 0) task += "(";
    task += numbers[i + 1];
    if ((braces[i] === 1 && braces[i + 1] !== 1) || i === braces.length)
      task += ")";
  }
  let answer;
  try {
    answer = eval(task);
  } catch (e) {
    console.log({ task, operations, braces, numbers });
  }
  return { task, answer };
};

const addNumber = (str, i) => {
  return `${i + 1}. ${str} = `.replaceAll("/", ":");
};

const printTask = (task) => {
  const tasksWithAnswers = new Array(TASKS_NUMBER).fill(0).map(() => task());
  const tasks = tasksWithAnswers
    .map((el, i) => addNumber(el.task, i))
    .join("\n");
  console.log(tasksWithAnswers);
  console.log(tasks);
};

// printTask(getMultipyTask);
// printTask(getDivisionTask);
// printTask(getComplexTask);
printTask(() => getComplexTaskWithBraces(10));
